Observer Pattern
3 min readObserver Pattern
TL;DR
The Observer pattern lets multiple subscribers react to an event stream — price ticks, order fills, status changes — without the publisher knowing who is listening, giving you loose coupling and easy fan-out. In .NET it is typically just an event/Action<T>; senior concerns are unsubscribing to avoid memory leaks, isolating subscriber failures, and knowing when to graduate to IObservable<T> or a message broker.
How it works
🧩 Example — PriceFeed with multiple subscribers
public class Tick
{
public string Symbol { get; init; }
public double Bid { get; init; }
public double Ask { get; init; }
}
public class PriceFeed
{
public event Action<Tick>? OnTick;
public void Publish(Tick tick)
{
OnTick?.Invoke(tick);
}
}
// --- Observers ---
public class ChartService
{
public void Subscribe(PriceFeed feed) => feed.OnTick += Display;
private void Display(Tick tick)
=> Console.WriteLine($"Chart updated: {tick.Symbol} = {tick.Bid}/{tick.Ask}");
}
public class AlertService
{
public void Subscribe(PriceFeed feed) => feed.OnTick += Alert;
private void Alert(Tick tick)
{
if (tick.Bid > 1.25)
Console.WriteLine($"🚨 Alert: {tick.Symbol} > 1.25");
}
}
// --- Usage ---
var feed = new PriceFeed();
var chart = new ChartService();
var alert = new AlertService();
chart.Subscribe(feed);
alert.Subscribe(feed);
feed.Publish(new Tick { Symbol = "GBPUSD", Bid = 1.2520, Ask = 1.2522 });
✅ Why it matters:
- Perfect for real-time streaming (price feeds, notifications, updates).
- Loose coupling between publisher and subscribers.
- Scales to multiple observers (UI, loggers, analytics, etc.).
Quick recall Q&A
Whenever multiple components need to react to the same event stream—market ticks, order fills, health changes—without tight coupling. It decouples publishers from subscribers so you can add/remove listeners without touching producers.
Observer is in-process and synchronous (events raised inside the same app), while pub/sub uses brokers for cross-process communication. Start with Observer for local notifications; graduate to brokers (RabbitMQ, Kafka) for distributed systems.
Wrap subscriber invocations in try/catch, run them asynchronously, or use mediator pipelines that isolate failures. Consider IObservable<T> + Rx to provide built-in error handling semantics.
Keep references to event handlers and detach them (feed.OnTick -= handler). With IObservable<T>, dispose the subscription. In DI scenarios, use weak references or lifetime-managed subscriptions.
IObservable<T>/Reactive Extensions instead of custom events?When you need advanced operators (buffering, throttling, filtering) or asynchronous streams. Rx provides a richer API and backpressure controls.
Push ticks to a message broker (RabbitMQ topics, Kafka) and let downstream services subscribe. The Observer concept still applies, but the transport ensures durability and fan-out across machines.
Execute callbacks on thread pool tasks, channels, or use asynchronous event handlers returning Task. Alternatively, push events into bounded queues so slow consumers don’t back up producers.
Combine with Strategy (different reaction logic per subscriber), Decorator (add logging around event handling), or CQRS (publish domain events that feed read models).
Subscribe fake handlers or use spies to capture events, then assert they received the expected sequence when the publisher produces certain ticks.
Document whether observers receive events in publish order; if ordering matters, process events synchronously per subscriber or use ordered queues. For distributed observers, leverage partitions/keys to maintain order.